package com.michaelbulava.TFS.vcs.internal;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FileStatus;
import com.intellij.openapi.vcs.actions.VcsContextFactory;
import com.intellij.openapi.vcs.changes.ChangeListManagerGate;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileVisitor;
import com.intellij.vcsUtil.VcsUtil;
import com.michaelbulava.TFS.log.FileLogger;
import com.michaelbulava.TFS.vc.tfsitem.TFSFile;
import com.michaelbulava.TFS.vcs.TFSVcs;
import com.michaelbulava.TFS.vcs.exceptions.UnableToCreateTFSFilePathException;
import com.michaelbulava.TFS.vcs.internal.TFSFiles.TFSFilePath;
import com.michaelbulava.TFS.vcs.utils.TFSFileUtil;
import com.michaelbulava.TFS.vcs.utils.TFSProgressUtil;
import com.microsoft.tfs.core.clients.versioncontrol.GetItemsOptions;
import com.microsoft.tfs.core.clients.versioncontrol.WorkspaceLocation;
import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.*;
import com.microsoft.tfs.core.clients.versioncontrol.specs.ItemSpec;
import com.microsoft.tfs.util.Check;
import com.sun.corba.se.spi.activation.Server;
//import com.sun.glass.ui.Pen;
import org.apache.commons.lang.ArrayUtils;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by mbulava on 5/7/2015.
 */

public class StatusProvider {
    private static final Logger log = FileLogger.getInstance(StatusProvider.class);

    public static void visitByStatus_V2(Workspace workspace, List<FilePath> roots, boolean recursive,
                                        ProgressIndicator indicator, Project project, ChangeListManagerGate addGate,
                                        StatusVisitor statusVisitor){
        Check.notNull(statusVisitor, "statusVisitor");
        if (workspace == null){
            log.warn("Unable to get workspace for project");
            return;
        }

        ItemSpec[] itemSpecs = TFSFileUtil.getItemSpecs(project, workspace, roots, recursive, false);
        if (itemSpecs.length == 0)
            return;
        TFSProgressUtil.setProgressText(indicator, "Retrieve extended items");

        Map<String, ExtendedItem> extendedItems = TFSFileUtil.getWorkspaceExtendedItems(project, workspace,
                itemSpecs);

        TFSProgressUtil.checkCanceled(indicator);

        AtomicReference<PendingChange[]> atomicCandidateChanges = new AtomicReference<>();
        TFSProgressUtil.setProgressText(indicator, "Retrieve Pending changes");
        PendingChange[] pendingChangesArray = new PendingChange[0];
        PendingChange[] candidateChanges = null;

        try{
            if (workspace.isLocalWorkspace()){
                pendingChangesArray = workspace.getPendingChangesWithCandidates(itemSpecs, true
                        , atomicCandidateChanges);
                if (atomicCandidateChanges != null && atomicCandidateChanges.get() != null)
                    candidateChanges = atomicCandidateChanges.get();
            } else {
                pendingChangesArray = workspace.getPendingChanges(itemSpecs, true).getPendingChanges();
            }
        } catch (Throwable tr){
            RuntimeException e2 = new RuntimeException("Failed to get pending changes", tr);
            log.error("failed to get pending changes", e2);
            throw e2;
        }

        Map<String, PendingChange> pendingChanges = new HashMap<String, PendingChange>();
        for(PendingChange pc : pendingChangesArray){
            pendingChanges.put(pc.getLocalItem(), pc);
        }
        pendingChangesArray = null;

        TFSProgressUtil.checkCanceled(indicator);
        for(FilePath root : roots){
            Collection<FilePath> localFiles = new HashSet<FilePath>();

            FilePath newRoot = null;
            //Lets work with TFSFilePath objects as much as possible
            if (!(root instanceof TFSFilePath))
                try{
                    newRoot = new TFSFilePath(workspace, root);
                    if (!recursive)
                        localFiles.add(newRoot);
                } catch (UnableToCreateTFSFilePathException e) {
                    log.info(String.format("Could not convert root FilePath [%s] to TFSFilePath", root.getPath()), e);
                    if (!recursive)
                        localFiles.add(root);
                }

            if (recursive){
                long startTime = System.currentTimeMillis();
                addExistingFilesRecursively(project, workspace, localFiles, root.getVirtualFile());
                long endTime = System.currentTimeMillis();
                log.info(String.format("addExistingFilesRecursively too %d milliseconds", (endTime - startTime)));
            }


            for(FilePath localFile : localFiles){
//                FileStatus internalStatus = null;
//                if (addGate != null && root.getVirtualFile() != null)
//                    internalStatus = addGate.getStatus(root.getVirtualFile());
                if (TFSVcs.isIgnored(project, localFile)){
                    pendingChanges.remove(localFile.getPresentableUrl());
                    extendedItems.remove(localFile.getPresentableUrl());

                    ServerStatus.Ignored.INSTANCE.visitBy(localFile, true, statusVisitor);
                    continue;
                }

                ExtendedItem extendedItem = null;
                PendingChange pendingChange = pendingChanges.remove(localFile.getPresentableUrl());


                if (localFile instanceof TFSFilePath){

                    extendedItem = ((TFSFilePath)localFile).getExtendedItem();
                    if (extendedItem != null)
                        extendedItems.remove(extendedItem.getLocalItem());

                } else {
                    extendedItem = extendedItems.remove(localFile.getPresentableUrl());
                    if (extendedItem == null)
                        extendedItem = TFSFileUtil.getExtendedItem(localFile.getPresentableUrl(), workspace);

                    determineServerStatus(pendingChange, extendedItem, workspace)
                            .visitBy(localFile, TFSFileUtil.localItemExists(localFile), statusVisitor);
                }

                boolean localItemExists = TFSFileUtil.localItemExists(localFile);

//                    if (pendingChange != null){
//                        if (!localItemExists && pendingChange.getChangeType().contains(ChangeType.DELETE)){
//                            localFile = VcsUtil.getFilePathForDeletedFile(localFile.getPath()
//                                    , pendingChange.getItemType() == ItemType.FOLDER);
//                        }
//                    }

                boolean isWorkingFolder = false;
                if (localFile.isDirectory()){
                    isWorkingFolder = TFSFileUtil.isWorkingFolder(localFile, workspace);
                }

                if (!isWorkingFolder){
                    determineServerStatus(pendingChange, extendedItem, workspace)
                            .visitBy(localFile, localItemExists, statusVisitor);
                }
                TFSProgressUtil.checkCanceled(indicator);
            }
        }
        if (recursive) {
            for (PendingChange pendingChange : pendingChanges.values()) {
                FilePath localFile = TFSFileUtil.getFilePath(pendingChange.getLocalItem(), workspace);

//                if (!TFSVcs.getInstance(project).isIgnoredFile(localFile)){
//                    ServerStatus.Ignored.INSTANCE.performVisitBy(localFile, true, statusVisitor);
//                    continue;
//                }

                ExtendedItem extendedItem = extendedItems.remove(pendingChange.getLocalItem());
                if (extendedItem == null)
                    extendedItem = TFSFileUtil.getExtendedItem(pendingChange.getLocalItem(), workspace);

//                if (extendedItem == null) {
//                    for (Map.Entry<Integer, ExtendedItem> entry : extendedItems.entrySet()) {
//                        if (entry.getValue().getItemID() == pendingChange.getItemID()) {
//                            extendedItem = entry.getValue();
//                            extendedItems.remove(entry.getKey());
//                            break;
//                        }
//                    }
//                }
                determineServerStatus(pendingChange, extendedItem, workspace)
                        .visitBy(localFile, TFSFileUtil.localItemExists(localFile), statusVisitor);

            }
            for (ExtendedItem item : extendedItems.values()) {
                //TODO look at this
                FilePath localFile = TFSFileUtil.getFilePath(workspace, item);
                if (item.getDeletionID() != 0 && !localFile.getIOFile().exists()) {
                    continue;
                }
                determineServerStatus(null, item, workspace)
                        .visitBy(localFile, TFSFileUtil.localItemExists(localFile), statusVisitor);
            }
        }
    }

    private static ServerStatus determineServerStatus(PendingChange pendingChange, @Nullable ExtendedItem item,
                                                      Workspace workspace){
        boolean localWorkspace = workspace.getLocation() == WorkspaceLocation.LOCAL;
        ChangeType change;
        if (item != null)
            change = item.getPendingChange();
        else if (pendingChange != null)
            change = pendingChange.getChangeType();
        else
            return new ServerStatus.UpToDate(localWorkspace);


        ChangeType IgnoreChanges = ChangeType.NONE.combine(ChangeType.LOCK);
        change.remove(IgnoreChanges);

        if (item != null && item.getLocalItem() == null && item.getSourceServerItem() != null){
            //TODO this is an incoming addition that should be merged.
            return new ServerStatus.OutOfDate(item);
        }

        if ((item != null && item.getLocalItem() == null) && change.isEmpty())
            return ServerStatus.NotVersioned.INSTANCE;


        if (change.isEmpty()){
            TFSVcs.assertTrue(item.getLatestVersion() != 0);
            if (item != null && item.getLocalVersion() < item.getLatestVersion())
                return new ServerStatus.OutOfDate(item);

            return new ServerStatus.UpToDate(item, localWorkspace);
        }


        if (!change.containsAny(ChangeType.ADD)){
            if (!change.containsAny(ChangeType.combine(new ChangeType[]{ ChangeType.MERGE, ChangeType.BRANCH })) ||
                    item.getLatestVersion() != 0){
                //TODO Hmmm?
            }
        } else {
            TFSVcs.assertTrue(change.containsAny(ChangeType.ENCODING));

            if (pendingChange != null)
                return new ServerStatus.ScheduledForAddition(pendingChange, localWorkspace);
            //Make sure we have the item...
            TFSVcs.assertTrue(item.getLatestVersion() == 0);
            TFSVcs.assertTrue(item.getLocalVersion() ==  0);
            return new ServerStatus.ScheduledForAddition(item, localWorkspace);
        }

        if (change.contains(ChangeType.DELETE)){
            if (pendingChange != null)
                return new ServerStatus.ScheduledForDeletion(pendingChange, localWorkspace);

            return new ServerStatus.ScheduledForDeletion(item, localWorkspace);
        }

        if (change.containsAny(ChangeType.combine(new ChangeType[]{ ChangeType.EDIT, ChangeType.MERGE,
                                                    ChangeType.ENCODING})) &&
                !change.contains(ChangeType.RENAME)){
            TFSVcs.assertTrue(item.getLatestVersion() != 0);
            if (item.getLocalVersion() != 0){
                TFSVcs.assertTrue(item.getLocalItem() != null);
                if (pendingChange != null)
                    return new ServerStatus.CheckedOutForEdit(pendingChange, localWorkspace);

                return new ServerStatus.CheckedOutForEdit(item, localWorkspace);
            }
            return new ServerStatus.ScheduledForAddition(item, localWorkspace);
        }

        if (change.containsAny(ChangeType.combine(new ChangeType[]{ChangeType.MERGE, ChangeType.RENAME})) &&
                !change.contains(ChangeType.EDIT)){
            if (item != null)
                return new ServerStatus.Renamed(item, workspace);
            else if (pendingChange != null)
                return new ServerStatus.Renamed(pendingChange, localWorkspace);

        }

        if (change.containsAll(ChangeType.combine(new ChangeType[]{ChangeType.RENAME, ChangeType.EDIT}))){
            TFSVcs.assertTrue(item.getLatestVersion() != 0);
            TFSVcs.assertTrue(item.getLocalVersion() != 0);
            TFSVcs.assertTrue(item.getLocalItem() != null);
            if (pendingChange != null)
                return new ServerStatus.RenamedCheckedOut(pendingChange, localWorkspace);

            return new ServerStatus.RenamedCheckedOut(item, workspace);
        }


        if (change.contains(ChangeType.UNDELETE)){
            if (pendingChange != null)
                return new ServerStatus.Undeleted(pendingChange, localWorkspace);
            return  new ServerStatus.Undeleted(item, localWorkspace);
        }

        log.error("Uncovered case for item " + (item.getLocalItem() != null ? item.getLocalItem() : item.getTargetServerItem()));

        return ServerStatus.NotVersioned.INSTANCE;
    }


    private static void addExistingFilesRecursively(final Project project, final Workspace workspace,
                                                    final Collection<FilePath> result, VirtualFile file){
        Check.notNull(result, "result");
        if (file != null && file.exists()){
            VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor(new VirtualFileVisitor.Option[0]) {

                @Override
                public boolean visitFile(VirtualFile file) {
                    Check.notNull(file, "file");

                    if (!TFSVcs.getInstance(project).isIgnoredFile(file)) {
                        result.add(TFSFileUtil.getFilePath(file.getPresentableUrl(), workspace));
                        return true;
                    } else {
                        result.add(TFSFileUtil.getNonTFSFilePath(file.getPresentableUrl()));
                        return true;
                    }
                }
            });
        }
    }

    private static boolean equals(FilePath path1, String path2){
        if (path2 == null)
            return path1 == null;

        return FileUtil.filesEqual(path1.getIOFile(), new File(path2));
    }
}
